#include <iostream>
#include <math.h>

#include "building_type.hpp"
#include "city.hpp"
#include "foreach.hpp"
#include "government.hpp"
#include "hex_geometry.hpp"
#include "occupation.hpp"
#include "pathfind.hpp"
#include "path_cost_calculators.hpp"
#include "path_utils.hpp"
#include "project.hpp"
#include "raster.hpp"
#include "texture.hpp"
#include "unit.hpp"
#include "unit_type.hpp"
#include "wml_node.hpp"
#include "wml_utils.hpp"
#include "world.hpp"

city::city(wml::const_node_ptr node, hex::tile& t, government_ptr gov)
  : owner_(gov), name_(node->has_attr("name") ? node->attr("name") : gov->generate_city_name()), image_(graphics::texture::get(node->attr("image"))), tile_(t), market_(gov), selected_(false)
{
	const std::vector<const_job_type_ptr>& job_types = job_type::jobs();
	foreach(const_job_type_ptr j, job_types) {
		jobs_.push_back(new job(j, &t));
	}

	wml::const_node_ptr buildings = node->get_child("buildings");
	if(buildings) {
		for(wml::node::const_attr_iterator i = buildings->begin_attr(); i != buildings->end_attr(); ++i) {
			buildings_[building_type::get(i->first)] = wml::get_int(buildings, i->first);
		}
	}
}

void city::new_turn(world& w)
{
	add_history(w);
	exports_.clear();
	imports_.clear();
	trade_routes_cache_.clear();

	//see if anyone wants to change to an occupation in the city.
	const std::vector<const occupation*>& occupations = occupation::all();
	foreach(const occupation* o, occupations) {
		if(o->available_in_city(this) == false) {
			continue;
		}

		const int capital_required = o->capital_required(w, this);

		hex::tile_ptr best_tile;
		int lowest_amount = 0;

		typedef std::pair<hex::location, int> influence_pair;
		foreach(const influence_pair& influence, influence_) {
			const hex::location& loc = influence.first;
			assert(w.map().is_loc_on_map(loc));
			hex::tile_ptr t = w.map().get_tile(loc);
			if(t->citizens().empty()) {
				continue;
			}

			if(t->citizens().back()->get_occupation() || t->citizens().back()->has_job()) {
				continue;
			}

			// if the citizen doesn't have enough money to enter into this
			// occupation.
			if(capital_required > 0 && t->citizens().back()->items_owned("silver") < capital_required) {
				continue;
			}

			const int wanted = t->citizens().back()->would_change_occupation(*o, market_, *t, t->citizens().size() - 1);
			if(!best_tile || wanted < lowest_amount) {
				best_tile = t;
				lowest_amount = wanted;
			}
		}

		if(best_tile) {
			const int earnings = o->earnings_estimate(w, this, *best_tile->citizens().back());
			std::cerr << "CITIZEN JOB: " << earnings << " >= " << lowest_amount << "\n";
			if(earnings >= lowest_amount) {
				tile_.citizens().push_back(best_tile->citizens().back());
				best_tile->citizens().pop_back();
				tile_.citizens().back()->change_occupation(o);
			}
		}
	}
}

void city::end_turn(world& w)
{
	for(std::vector<unit_in_production>::iterator i = units_in_production_.begin(); i != units_in_production_.end(); ) {
		if(!pay_upkeep_for_unit(i->unit)) {
			i->unit->disband();
			i = units_in_production_.erase(i);
			continue;
		}

		i->training_done++;
		if(i->training_done >= i->training_required) {
			w.add_unit(tile().loc(), i->unit);
			i = units_in_production_.erase(i);
		} else {
			++i;
		}
	}
}

bool city::pay_upkeep_for_unit(const_unit_ptr u)
{
	//for now all units have two food in upkeep costs.
	const_item_type_ptr food = item_type::get("food");
	return pay_upkeep(food) && pay_upkeep(food);
}

void city::draw(const world& w) const
{
	tile_.draw(w.map().heights(), graphics::texture::get("textures/city.png"), 0.01);
/*
	const GLfloat translate_x = tile_translate_x(tile_.loc());
	const GLfloat translate_x = tile_translate_x(tile_.loc());
	const int pixel_x = tile_pixel_x(tile_.loc());
	const int pixel_y = tile_pixel_y(tile_.loc());
	if(selected_) {
		++selected_;
		glColor4f(1.0, 1.0, 0.8 + sin(selected_/10.0)*0.2, 1.0);
	}
	graphics::blit_texture(image_, pixel_x, pixel_y);
	glColor4f(1.0, 1.0, 1.0, 1.0);

	foreach(const project_ptr& p, projects_) {
		p->draw();
	}
*/
}

int city::revenue() const
{
	return market_.last_tax_revenue();
}

int city::expenses() const
{
	int result = 0;
	foreach(const job_ptr& j, jobs_) {
		result += j->pay()*j->positions_taken();
	}

	return result;
}

void city::calculate_influence(world& w)
{
	influence_.clear();
	hex::get_reachable_tiles(w, tile_.loc(), 15, influence_);
	for(std::map<hex::location, int>::iterator i = influence_.begin();
	    i != influence_.end(); ++i) {
		assert(w.map().get_tile(i->first));
		w.map().get_tile(i->first)->add_influence(this);
	}
}

void city::prepare_trades(world& w)
{
	market_.clear();
	for(std::map<hex::location, int>::const_iterator i = influence_.begin();
	    i != influence_.end(); ++i) {
		const hex::tile_ptr t = w.map().get_tile(i->first);
		if(!t) {
			continue;
		}

		foreach(citizen_ptr c, t->citizens()) {
			c->place_orders(market_);
		}
	}

	market_.prepare_market();
}

void city::execute_trades(world& w)
{
	market_.resolve_market();
}

std::string city::description() const
{
	return market_.description();
}

void city::get_items(const_item_type_ptr i, int quantity)
{
	items_[i] += quantity;
}

int city::items_owned(const_item_type_ptr i) const
{
	items_map::const_iterator itor = items_.find(i);
	if(itor != items_.end()) {
		return itor->second;
	} else {
		return 0;
	}
}

int city::storage_capacity(const_item_type_ptr item) const
{
	int result = 0;
	for(buildings_map::const_iterator i = buildings_.begin(); i != buildings_.end(); ++i) {
		result += i->first->storage(item->id()) * i->second;
	}

	return result;
}

government_ptr city::owner()
{
	return owner_;
}

const_government_ptr city::owner() const
{
	return owner_;
}

void city::fill_jobs(world& w)
{
	foreach(job_ptr j, jobs_) {
		int cost = add_worker_cost(w, j);
		while(j->positions_available() > 0 && cost > 0) {
			hire_worker(w, j);
			j->set_positions_available(j->positions_available() - 1);
			cost = add_worker_cost(w, j);
		}

		if(cost > 0) {
			j->set_pay(cost);
		}
	}
}

int city::add_worker_cost(const world& w, const_job_ptr j, hex::location* loc) const
{
	int min_cost = -1;
	typedef std::pair<hex::location, int> TilePair;
	foreach(const TilePair& t, influence_) {
		const hex::const_tile_ptr tile = w.map().get_tile(t.first);
		if(!tile || tile->citizens().empty() || tile->citizens().back()->has_job()) {
			continue;
		}

		const int cost = tile->citizens().back()->would_accept_job(j, market_, *tile, tile->citizens().size() - 1);
		if(min_cost == -1 || cost < min_cost) {
			min_cost = cost;
			if(loc) {
				*loc = t.first;
			}
		}
	}

	return min_cost;
}

void city::hire_worker(world& w, job_ptr j)
{
	hex::location loc;
	const int cost = add_worker_cost(w, j, &loc);
	if(cost < 0 || !w.map().is_loc_on_map(loc)) {
		return;
	}

	hex::tile_ptr t = w.map().get_tile(loc);
	assert(t);
	assert(t->citizens().empty() == false);
	t->citizens().back()->set_job(j);
	j->set_pay(cost);
	j->set_positions_taken(j->positions_taken() + 1);
}

void city::pay_workers()
{
	int pay = 0;
	static const std::string MoneyStr = "silver";
	foreach(citizen_ptr c, tile_.citizens()) {
		if(c->has_job()) {
			c->get_item(MoneyStr, c->job()->pay());
			pay += c->job()->pay();
		}
	}

	owner_->get_money(-pay);
}

bool city::recruit_unit(world& w, const_unit_type_ptr type)
{
	citizen_ptr c = recruit_citizen_for_unit(w, type);
	if(!c) {
		return false;
	}
	unit_in_production u;
	u.unit.reset(new unit(type, owner_, this, c));
	u.training_done = 0;
	u.training_required = type->cost();
	units_in_production_.push_back(u);
	return true;
}

citizen_ptr city::recruit_citizen_for_unit(world& w, const_unit_type_ptr type)
{
	//TODO: do a better job selecting a citizen to join a unit.
	typedef std::pair<hex::location, int> TilePair;
	foreach(const TilePair& t, influence_) {
		const hex::tile_ptr tile = w.map().get_tile(t.first);
		if(!tile || tile->citizens().empty() || tile->citizens().back()->has_job()) {
			continue;
		}

		citizen_ptr result = tile->citizens().back();
		result->change_occupation(NULL);
		tile->citizens().pop_back();
		return result;
	}

	return citizen_ptr();
}

int city::engineering_production() const
{
	int result = 0;
	foreach(const job_ptr& j, jobs()) {
		if(j->type()->id() == "engineer") {
			result += j->positions_taken();
		}
	}

	return result*5;
}

void city::add_building(const_building_type_ptr type)
{
	buildings_[type]++;
}

void city::add_project(project_ptr p)
{
	projects_.push_back(p);
}

void city::remove_project(project_ptr p)
{
	projects_.erase(std::find(projects_.begin(), projects_.end(), p));
}

void city::work_on_projects(world& w)
{
	int labor = engineering_production();
	while(projects_.empty() == false && labor > 0) {
		const int remaining_labor = projects_.front()->labor_required() - projects_.front()->labor_complete();
		projects_.front()->apply_labor(labor);
		labor -= remaining_labor;
		if(projects_.front()->complete()) {
			projects_.front()->apply_results(w);
			projects_.erase(projects_.begin());
		}
	}
}

std::vector<const_unit_type_ptr> city::can_recruit() const
{
	std::vector<const_unit_type_ptr> result;
	foreach(const std::string& key, owner_->can_recruit()) {
		result.push_back(unit_type::get(key));
	}

	return result;
}

namespace {
struct BuildingNotBuildable {
	bool operator()(const const_building_type_ptr& type) const {
		return type->cost() <= 0;
	}
};
}

std::vector<const_building_type_ptr> city::can_build() const
{
	std::vector<const_building_type_ptr> result = building_type::get_all();
	result.erase(std::remove_if(result.begin(), result.end(), BuildingNotBuildable()), result.end());
	return result;
}

int city::get_shortest_trade_path(const world& w, const const_city_ptr& other,
                                  const std::vector<hex::location>** path) const
{
	trade_route& route = trade_routes_cache_[other];
	if(route.cost == 0) {
		route.cost = hex::find_path(tile_.loc(), other->tile_.loc(),
		                            move_cost_calculator(w.map()),
		                            &route.path);
	}

	if(path) {
		*path = &route.path;
	}

	return route.cost;
}

void city::add_export(const merchant_trade& trade)
{
	exports_.push_back(trade);
}

void city::add_import(const merchant_trade& trade)
{
	imports_.push_back(trade);
}

void city::add_history(world& w)
{
	history_entry entry;
	entry.population = tile_.citizens().size();
	entry.influence = 0;
	entry.deaths = 0;
	for(std::map<hex::location, int>::const_iterator i = influence_.begin(); i != influence_.end(); ++i) {
		hex::const_tile_ptr t = w.map().get_tile(i->first);
		assert(t);
		entry.influence += t->citizens().size();
		entry.deaths += t->causes_of_deaths().size();
	}
	history_.push_back(entry);
}

variant city::get_value(const std::string& key) const
{
	return variant();
}

bool city::pay_upkeep(const_item_type_ptr type)
{
	int& nowned = items_[type];
	if(nowned > 0) {
		--nowned;
		return true;
	}

	const int price = market_.can_buy_at(type);
	if(price < 0 || price > owner_->treasury()) {
		return false;
	}

	market_.buy_item(type, price);
	owner_->get_money(-price);
	return true;
}
